/*
 * File     : X86CDeclUnixNativeCallFactory.java
 * Created  : 28 Apr 2011
 *
 * Copyright © 2011 Matthew Wilson (mj. {my-surname} .uk {at} gmail.com)
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.googlecode.dni.internal.call;

import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;

import com.googlecode.dni.internal.DniInternal;
import com.googlecode.dni.internal.library.DynamicLibrary;
import com.googlecode.dni.library.Function.LastErrorBehaviour;
import com.googlecode.dni.type.Pointer;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;


/**
 * <p>
 *  Implementation of {@link NativeCallFactory} that produces native methods for
 *  calling X86 CDecl (Unix flavour) methods from X86 Unix.
 * </p>
 *
 * @author Matthew Wilson
 */
public final class X86CDeclUnixNativeCallFactory extends BaseNativeCallFactory
{

    private static final DynamicLibrary LIBRARY;

    /** JNI function for one 32-bit parameter. */
    private static final Pointer CALL_1;

    /** JNI function for two 32-bit parameters. */
    private static final Pointer CALL_2;

    /** JNI function for three 32-bit parameters. */
    private static final Pointer CALL_3;

    /** JNI function for four 32-bit parameters. */
    private static final Pointer CALL_4;

    /** JNI function for more than four 32-bit parameters. */
    private static final Pointer CALL_DISPLACE_4;

    /**
     * JNI function for more than four 32-bit parameters, with a 64-bit
     * parameter that would have otherwise been split.
     */
    private static final Pointer CALL_DISPLACE_5;

    /** JNI function for 'n' 32-bit parameters. */
    private static final Pointer CALL_N;

    static
    {
        LIBRARY = DniInternal.PLATFORM.getDniDynamicLibrary();
        try
        {
            CALL_1          = LIBRARY.getSymbol( "dni_x86_cdecl_unix_call_1" );
            CALL_2          = LIBRARY.getSymbol( "dni_x86_cdecl_unix_call_2" );
            CALL_3          = LIBRARY.getSymbol( "dni_x86_cdecl_unix_call_3" );
            CALL_4          = LIBRARY.getSymbol( "dni_x86_cdecl_unix_call_4" );
            CALL_DISPLACE_4 = LIBRARY.getSymbol( "dni_x86_cdecl_unix_call_displace_4" );
            CALL_DISPLACE_5 = LIBRARY.getSymbol( "dni_x86_cdecl_unix_call_displace_5" );
            CALL_N          = LIBRARY.getSymbol( "dni_x86_cdecl_unix_call_n" );
        }
        catch ( Throwable throwable )
        {
            try
            {
                LIBRARY.free();
            }
            catch ( Throwable throwable2 )
            {
                DniInternal.LOGGER.log( Level.SEVERE, "Failed to free library", throwable2 );
            }
            throw new ExceptionInInitializerError( throwable );
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressFBWarnings( value = "WOC_WRITE_ONLY_COLLECTION_LOCAL",
                         justification = "False positive" )
    Pointer getNativeBinding( final List< Parameter > returnParameterContainer,
                              final List< Parameter > parameters,
                              final Pointer functionPointer, final LastErrorBehaviour lastErrorBehaviour )
    {
        // TODO v1.0 handling returning structs-by-value

        if ( parameters.isEmpty() )
        {
            // no shuffling to do; use the raw function pointer
            return functionPointer;
        }

        if ( parameters.get( parameters.size() - 1 ) instanceof VariableArityParameter )
        {
            // TODO v1.1 support varargs
            throw new UnsupportedOperationException( "TODO implement me" );
//            return getVariableArityNativeBinding( parameters );
        }

        // First four 32-bit-sized parameters get shuffled to the end to make
        // way for:
        // - JNIEnv*
        // - jclass
        // - fnptr
        // - offset to displaced parameters

        // Catch: a long or double might be split across arg3 and arg4.
        // When this happens, displace the first five.

        List< Parameter > firstFour = new ArrayList< Parameter >( 4 );

        // count up the number of 32-bit-sized parameters
        int displacementOffset = 20;
        int firstFourSize = 0;
        int wordCount = 0;
        for ( Parameter parameter : parameters )
        {
            int size = 1;
            if ( parameter.getNativeType() == 'J' ||
                 parameter.getNativeType() == 'D' )
            {
                size = 2;
            }

            if ( firstFourSize < 4 )
            {
                firstFour.add( parameter );
                firstFourSize += size;
            }
            else
            {
                displacementOffset += size * 4;
            }

            wordCount += size;
        }

        // add the function pointer
        parameters.add( 0, new FunctionPointerParameter( functionPointer ) );

        Pointer nativeBinding;

        switch ( wordCount )
        {
            case 1:
                nativeBinding = CALL_1;
                break;

            case 2:
                nativeBinding = CALL_2;
                break;

            case 3:
                nativeBinding = CALL_3;
                break;

            case 4:
                nativeBinding = CALL_4;
                break;

            default:
                if ( wordCount > 128 )
                {
                    // Java 'only' allows 256 method arguments...
                    nativeBinding = getNativeBindingLarge( parameters );
                }
                else
                {
                    nativeBinding = getNativeBindingDisplaced( parameters,
                                                               firstFour,
                                                               displacementOffset,
                                                               firstFourSize == 5 );
                }
        }

        return nativeBinding;
    }

    /**
     * Reorders the parameters for the case with more than four 32-bit
     * parameters.
     *
     * @param parameters
     *            all parameters
     * @param firstFour
     *            the parameters making up the first four slots
     * @param displacementOffset
     *            the offset from the stack pointer to the displaced arguments
     * @param displace5
     *            whether there was a 64-bit argument that would have been split
     *            by being displaced; instead, the whole argument is displaced
     *            and a padding 32-bit word inserted
     *
     * @return the native binding
     */
    private static Pointer getNativeBindingDisplaced( final List< Parameter > parameters,
                                                      final List< Parameter > firstFour,
                                                      final int displacementOffset,
                                                      final boolean displace5 )
    {
        // move them to the back
        parameters.removeAll( firstFour );
        parameters.addAll( firstFour );

        // add the offset
        parameters.add( 1, new ConstantIntegerParameter( displacementOffset ) );

        if ( displace5 )
        {
            // five 32-bit arguments were displaced;
            // the last one was a 64-bit argument

            // add padding to reserve room for the second half of the 64-bit
            // argument that was displaced
            parameters.add( 2, new PaddingParameter( 'I' ) );

            return CALL_DISPLACE_5;
        }

        return CALL_DISPLACE_4;
    }

    /**
     * Uses a temporary buffer for passing the arguments.
     *
     * @param parameters
     *            the parameters
     * @return the native binding
     */
    private static Pointer getNativeBindingLarge( final List< Parameter > parameters )
    {
        // use a 'large parameter' to pass in a pointer to the args
        List< Parameter > mainParameters = parameters.subList( 1, parameters.size() );
        LargeParameter32 largeParameter = new LargeParameter32( mainParameters );
        mainParameters.clear();

        // pass in the pointer to the arguments
        parameters.add( largeParameter );

        // pass in the count of arguments
        parameters.add( new ConstantIntegerParameter( largeParameter.getWords() ) );

        return CALL_N;
    }

}
